require(tidyverse)
require(data.table)
require(lubridate)
require(stringr)
require(ggvis)
require(ggplot2)
setwd(dir="D:\\Users\\gdrtmh\\Desktop\\Kaggle\\Jobs on Naukri")
inputdt <- as.data.table(read.csv(file="naukri_com-job_sample.csv", strip.white = TRUE, 
                             na.strings=c("","NA","Not Mentioned","Not Disclosed by Recruiter"), 
                             stringsAsFactors = FALSE))
# setting "Not mentioned" as NA for experience etc.
# setting "Not Disclosed by Recruiter" that is the norm for NA in payrate

Data Exploration

glimpse(inputdt)
Observations: 22,000
Variables: 14
$ company             <chr> "MM Media Pvt Ltd", "find live infotech", "Softtech Career Infosys...
$ education           <chr> "UG: B.Tech/B.E. - Any Specialization PG:Any Postgraduate - Any Sp...
$ experience          <chr> "0 - 1 yrs", "0 - 0 yrs", "4 - 8 yrs", "11 - 15 yrs", "6 - 8 yrs",...
$ industry            <chr> "Media / Entertainment / Internet", "Advertising / PR / MR / Event...
$ jobdescription      <chr> "Job Description   Send me Jobs like this Qualifications: - == > ...
$ jobid               <dbl> 210516002263, 210516002391, 101016900534, 81016900536, 12091600212...
$ joblocation_address <chr> "Chennai", "Chennai", "Bengaluru", "Mumbai, Bengaluru, Kolkata, Ch...
$ jobtitle            <chr> "Walkin Data Entry Operator (night Shift)", "Work Based Onhome Bas...
$ numberofpositions   <int> NA, 60, NA, NA, 4, NA, 2, 20, 2, NA, NA, NA, 2, NA, NA, 2, 3, NA, ...
$ payrate             <chr> "1,50,000 - 2,25,000 P.A", "1,50,000 - 2,50,000 P.A. 20000", NA, N...
$ postdate            <chr> "2016-05-21 19:30:00 +0000", "2016-05-21 19:30:00 +0000", "2016-10...
$ site_name           <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ skills              <chr> "ITES", "Marketing", "IT Software - Application Programming", "Acc...
$ uniq_id             <chr> "43b19632647068535437c774b6ca6cf8", "d4c72325e57f89f364812b5ed5a79...

site_name

summary(as.factor(inputdt$site_name))
www.naukri.com           NA's 
          3987          18013 
inputdt$site_name <- NULL

Well, it seems like the site name is pretty filled with either naukri or NA data. This column could probably be removed without influencing the subsequent data analysis.

uniq_id

uniqueN(inputdt$uniq_id)
[1] 22000

This Would be useful as an unique identifier for individual job listing.

jobid

uniqueN(inputdt, by=c("jobid"))
[1] 21910

It is not exactly clear how “jobid” is structed, perhaps it accounts of relisting of jobs?

Although, since the number of unique jobid is only differing from uniq_id by 90, if the purpose where to depict the relisting of respective jobs, it probably did not do its job very well.

Payrate

# Seeing that payrate listing would corresponds to format of "1,50,000 - 2,50,000 P.A"
# I would just use gsub to remove strings after P.A
inputdt$payrate.M<-gsub( " P.A.*$", "", inputdt$payrate)
# this is essential to remove numerical characters after the P.A string
# this also removed certain listed incentive/commission information in payrate
# stripping all the non numerical chr from the payrate
inputdt$payrate.M<-gsub("[^0-9-]", "", inputdt$payrate.M)
# inputdt$m.payrate<-gsub("[^0-9,-,-]", "", inputdt$m.payrate)
# Outliers
# Example of some payrate that is not extracated properly ( with 2 "-" etc after regexp treatment), 
# seeing there is no easy solution and that there is only ~200 of them, them will be ignored for now
inputdt[lengths(strsplit(inputdt$payrate.M, "-"))>2, list(payrate,payrate.M),]

Splitting the payrate column while conditionally ignoring those that have more than dash (“-”) symbol.

# Identifying the appropriate number of splits for payrate with str "-"
splits <- max(lengths(strsplit(inputdt$payrate.M, "-")))
# Spliting the payrate into m.payrate1 and m.payrate2, Ignoring those column with additional splits
mdt <- inputdt[lengths(strsplit(inputdt$payrate.M, "-"))<=2,
               paste0("m.payrate", 1:2) := tstrsplit(payrate.M, "-", fixed=TRUE)][] 
# Changing the column into numerics
mdt$m.payrate1<- as.numeric(mdt$m.payrate1)
mdt$m.payrate2<- as.numeric(mdt$m.payrate2)
# Creating a column that indicate the mean of the payrate, would be easier on plotting etc
mdt[, m.payrate.Mean := rowMeans(.SD), by = uniq_id, .SDcols = c("m.payrate1", "m.payrate2")]
# Checking if the values in experience1 are always lower than experience2, 
# such that experience1 can be considered as a lower limit for the job, vice versa for experience2
summary(mdt$m.payrate1>mdt$m.payrate2)
   Mode   FALSE    TRUE    NA's 
logical    4743       2   17255 
#    Mode   FALSE    TRUE    NA's 
# logical       2    4743   17255 
# listing the data with m.payrate1 > m.payrate2
mdt[m.payrate1>m.payrate2,list(payrate,payrate.M,m.payrate1,m.payrate2)]

In both of these cases, the payrate is not well formated and extracted. There is probably some bad extraction in the extracting of payrate that is not depicted here as well as they may be hidden behind m.payrate2 > m.payrate1.

Removing these cases (where m.payrate1>m.payrate2) by setting them to NA

mdt[(m.payrate1>m.payrate2),`:=`(payrate.M= NA,
                                 m.payrate1= NA,
                                 m.payrate2= NA,
                                 m.payrate.Mean= NA)]
# The majority of the records has no payrate listed.
# summary(mdt$m.payrate.Mean)
#      Min.   1st Qu.    Median      Mean   3rd Qu.      Max.      NA's 
# 9.000e+00 2.500e+05 3.750e+05 3.672e+09 8.000e+05 1.740e+13     17255 
# The maximum figure also points to likely error in extracation
# after attempting a few threshold of cut, I find 9e+6 to be most suitable, listed below are a table depicted payrate after this threshold.
mdt[m.payrate.Mean>9e+6] %>%
  .[,list(payrate, m.payrate.Mean)]
mdt[m.payrate.Mean>9e+6,`:=`(payrate.M= NA,
                             m.payrate1= NA,
                             m.payrate2= NA,
                             m.payrate.Mean= NA)]
#total count of valid payrate records
sum(!is.na(mdt$m.payrate.Mean))
[1] 4656
# all_values <- function(x) {
#   if(is.null(x)) return(NULL)
#   paste0(names(x), ": ", format(x), collapse = "<br />")
# }
mdt[!is.na(m.payrate.Mean),][m.payrate.Mean<9e+6] %>%
  melt(.,  measure.vars = patterns("^m.payrate")) %>%
  ggvis(~value, fill = ~variable) %>%
  group_by(variable) %>%
  layer_densities()  

skills

head(inputdt$skills)
[1] "ITES"                                  "Marketing"                            
[3] "IT Software - Application Programming" "Accounts"                             
[5] "IT Software - Application Programming" "IT Software - Application Programming"
unique(inputdt$skills)
 [1] "ITES"                                    "Marketing"                              
 [3] "IT Software - Application Programming"   "Accounts"                               
 [5] "Production"                              "Sales"                                  
 [7] "IT Software - Other"                     "Executive Assistant"                    
 [9] "IT Software - Mobile"                    "Engineering Design"                     
[11] "Financial Services"                      "Hotels"                                 
[13] "IT Software - QA & Testing"              "HR"                                     
[15] "Supply Chain"                            "IT Software - Network Administration"   
[17] "Architecture"                            "Legal"                                  
[19] "Site Engineering"                        "Journalism"                             
[21] NA                                        "IT Software - DBA"                      
[23] "Strategy"                                "Medical"                                
[25] "Design"                                  "Defence Forces"                         
[27] "IT Software - Mainframe"                 "IT Software - Telecom Software"         
[29] "IT Software - Embedded"                  "IT Software - Middleware"               
[31] "Teaching"                                "IT Software - System Programming"       
[33] "IT Software - Client/Server Programming" "Travel"                                 
[35] "IT Software - eCommerce"                 "TV"                                     
[37] "Fashion Designing"                       "IT Software - ERP"                      
[39] "IT Hardware"                             "Analytics & Business Intelligence"      
[41] "Beauty/Fitness/Spa Services"             "Top Management"                         
[43] "Export"                                  "IT Software - Systems"                  
[45] "Packaging"                               "Shipping"                               
inputdt[,.N,by=list(skills)] %>%
  .[,.SD[order(-N)]] %>%
  .[, head(.SD, 10),]%>%
  ggvis(x=~skills, y=~N) %>%
  layer_bars()%>%
  add_axis("x", properties = axis_props(
    labels = list(angle = 45, align = "left", fontSize = 10)
  ))

industry

# Checking whether every val  id entry has "yrs" in the records, 
summary(str_detect(mdt$experience, "yrs"))  
   Mode    TRUE    NA's 
logical   21885     115 
splits <- max(lengths(strsplit(mdt$industry, "/")))
mdt <- mdt[,paste0("industryS", 1:splits) := tstrsplit(industry, "/", fixed=TRUE)][]
melt(mdt,  measure.vars = patterns("^industryS"))

Experience

Date Time

head(mdt$postdate)

#str split the postdate based on format corresponds to "2016-05-21 19:30:00 +0000" on "+"
mdt[, c("postdate_time","postdate_timezone") := tstrsplit(postdate,"+",2)]

# summary(as.factor(mdt$postdate_timezone))
# 0000  NA's 
# 21977    23 
# it seems timezone is pretty meaningless in the data base too

mdt$postdate_timezone <- NULL

Changing the datetime into appropriate format

mdt$postdate_time <- parse_date_time(mdt$postdate_time, "%Y-%m-%d H:M:S")
#ignoring timezone for now, shouldn't make any difference too

Location

# some job listing are listed at multiple location
# "Delhi NCR, Mumbai, Bengaluru, Kochi, Greater Noida, Gurgaon, Hyderabad, Kozhikode, Lucknow"

splits <- max(lengths(strsplit(mdt$joblocation_address, ",")))

# checking 
# mdt[lengths(strsplit(mdt$joblocation_address, ","))==splits]

mdt <- mdt[,paste0("locationS", 1:splits) := tstrsplit(joblocation_address, ",", fixed=TRUE)] %>%
  
  
  melt(mdt,  measure.vars = patterns("^locationS"))


dt.location <- mdt[,paste0("location", 1:splits) ][, ID := NULL]
# 
# # Sort term_frequency in descending order
# term_frequency<- sort(mdt$location,decreasing = T)
# 
# # Plot a barchart of the 10 most common words
# barplot(term_frequency[1:10], col = "tan", las = 2)

Job description

require(tm)
require(quanteda)
require(wordcloud)
require(slam) # for row_sums
clean_VC <- VCorpus(VectorSource(mdt$jobdescription)) %>%
  tm_map(removePunctuation) %>%
  tm_map(removeNumbers) %>%
  tm_map(tolower)  %>%
  tm_map(removeWords, stopwords("english")) %>%
  tm_map(stripWhitespace) %>%
  tm_map(PlainTextDocument) %>%
  tm_map(stemDocument, language = "english")

clean_VC.tdm <- TermDocumentMatrix(clean_VC)

# Calculate the rowSums: term_frequency
# Notice that tdm is actually simple-triplet_matrix class,
# which is record as sparse matrix and has its own rowsum function
# class(clean_VC.tdm)
#  "TermDocumentMatrix"    "simple_triplet_matrix"

term_frequency<-row_sums(clean_VC.tdm)


# Sort term_frequency in descending order
term_frequency<- sort(term_frequency,decreasing = T)

# Plot a barchart of the 10 most common words
barplot(term_frequency[1:10], col = "tan", las = 2)

# Create word_freqs
word_freqs <- data.frame(term = names(term_frequency), num = term_frequency)
head(word_freqs)


wordcloud(word_freqs$term, word_freqs$num, max.words = 100, 
           random.order=FALSE, rot.per=0.35, colors=brewer.pal(8,"Dark2"))

clean_VC_sparse_r40<-removeSparseTerms(clean_VC.tdm, 0.4)
tdm<-sort(rowSums(as.matrix(clean_VC_sparse_r40)), decreasing  = TRUE)
word_freqs <- data.frame(term = names(tdm), num = tdm)

set.seed(142)   
wordcloud(word_freqs$term, word_freqs$num, min.freq=20, scale=c(5, .1), colors=brewer.pal(6, "Dark2"))   
clean_VC_sparse_r10<-removeSparseTerms(clean_VC.tdm, 0.1)
 tdm<-as.matrix(clean_VC_sparse_r10)
 
comparison.cloud(tdm, colors = c("orange", "blue"), max.words = 50)
library(RColorBrewer)
commonality.cloud(tdm, random.order=FALSE, scale=c(5, .5),colors = brewer.pal(4, "Dark2"), max.words=400)
docs <-VC %>%
  tm_map(removePunctuation) %>%
  tm_map(removeNumbers) %>%
  tm_map(tolower)  %>%
  tm_map(removeWords, stopwords("english")) %>%
  tm_map(stripWhitespace) %>%
  tm_map(PlainTextDocument)

myCorpusCopy<- docs ## creating a copy to be used as a dictionary

# docs <- docs %>%
#   tm_map(stemDocument) %>%
#   tm_map(stemCompletion, dictionary = myCorpusCopy)

docs.tdm<-TermDocumentMatrix(docs)

term_frequency<-row_sums(clean_VC.tdm)

# Sort term_frequency in descending order
term_frequency<- sort(term_frequency,decreasing = T)

# Plot a barchart of the 10 most common words
barplot(term_frequency[1:10], col = "tan", las = 2)

require(wordcloud)

# Create word_freqs
word_freqs <- data.frame(term = names(term_frequency), num = term_frequency)
head(word_freqs)


wordcloud(word_freqs$term, word_freqs$num, max.words = 100, 
           random.order=FALSE, rot.per=0.35, colors=brewer.pal(8,"Dark2"))
inaugdfm <- dfm(mdt$jobdescription, remove = stopwords("english"), 
                stem = TRUE, groups=mdt$industry1, ngrams = 2)
inaugdfm <- dfm(mdt$jobdescription)
                
                
tdm_trim<-t(dfm_trim(inaugdfm, max_docfreq = .8))
head(tdm_trim)

term_frequency<-row_sums(tdm_trim)

# Sort term_frequency in descending order
term_frequency<- sort(term_frequency,decreasing = T)

word_freqs <- data.frame(term = names(term_frequency), num = term_frequency)

wordcloud(word_freqs$term, word_freqs$num, max.words = 100, 
           random.order=FALSE, rot.per=0.35, colors=brewer.pal(8,"Dark2"))
docs<-c(mdt$jobdescription, mdt$industry1)
data(data_corpus_inaugural)


data.frame(inaugSpeech = texts(mdt$jobdescription), docvars(as.factor(mdt$industry1)))
            
            
myCorpus <- corpus_subset(data.frame(mdt$jobdescription,)


myStemMat <- dfm(myCorpus, remove = stopwords("english"), stem = TRUE, remove_punct = TRUE)
myStemMat[, 1:5]
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQoNCmBgYHtyfQ0KcmVxdWlyZSh0aWR5dmVyc2UpDQpyZXF1aXJlKGRhdGEudGFibGUpDQpyZXF1aXJlKGx1YnJpZGF0ZSkNCnJlcXVpcmUoc3RyaW5ncikNCnJlcXVpcmUoZ2d2aXMpDQpyZXF1aXJlKGdncGxvdDIpDQpgYGANCg0KYGBge3J9DQpzZXR3ZChkaXI9IkQ6XFxVc2Vyc1xcZ2RydG1oXFxEZXNrdG9wXFxLYWdnbGVcXEpvYnMgb24gTmF1a3JpIikNCmlucHV0ZHQgPC0gYXMuZGF0YS50YWJsZShyZWFkLmNzdihmaWxlPSJuYXVrcmlfY29tLWpvYl9zYW1wbGUuY3N2Iiwgc3RyaXAud2hpdGUgPSBUUlVFLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmEuc3RyaW5ncz1jKCIiLCJOQSIsIk5vdCBNZW50aW9uZWQiLCJOb3QgRGlzY2xvc2VkIGJ5IFJlY3J1aXRlciIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKSkNCg0KIyBzZXR0aW5nICJOb3QgbWVudGlvbmVkIiBhcyBOQSBmb3IgZXhwZXJpZW5jZSBldGMuDQojIHNldHRpbmcgIk5vdCBEaXNjbG9zZWQgYnkgUmVjcnVpdGVyIiB0aGF0IGlzIHRoZSBub3JtIGZvciBOQSBpbiBwYXlyYXRlDQoNCmBgYA0KDQojIERhdGEgRXhwbG9yYXRpb24NCg0KYGBge3J9DQpnbGltcHNlKGlucHV0ZHQpDQpgYGANCg0KIyMgc2l0ZV9uYW1lDQpgYGB7cn0NCnN1bW1hcnkoYXMuZmFjdG9yKGlucHV0ZHQkc2l0ZV9uYW1lKSkNCmlucHV0ZHQkc2l0ZV9uYW1lIDwtIE5VTEwNCmBgYA0KV2VsbCwgaXQgc2VlbXMgbGlrZSB0aGUgc2l0ZSBuYW1lIGlzIHByZXR0eSBmaWxsZWQgd2l0aCBlaXRoZXIgbmF1a3JpIG9yIE5BIGRhdGEuIFRoaXMgY29sdW1uIGNvdWxkIHByb2JhYmx5IGJlIHJlbW92ZWQgd2l0aG91dCBpbmZsdWVuY2luZyB0aGUgc3Vic2VxdWVudCBkYXRhIGFuYWx5c2lzLg0KDQojIyB1bmlxX2lkDQoNCmBgYHtyfQ0KdW5pcXVlTihpbnB1dGR0JHVuaXFfaWQpDQpgYGANCg0KVGhpcyBXb3VsZCBiZSB1c2VmdWwgYXMgYW4gdW5pcXVlIGlkZW50aWZpZXIgZm9yIGluZGl2aWR1YWwgam9iIGxpc3RpbmcuDQoNCiMjIGpvYmlkDQoNCmBgYHtyfQ0KdW5pcXVlTihpbnB1dGR0LCBieT1jKCJqb2JpZCIpKQ0KYGBgDQoNCkl0IGlzIG5vdCBleGFjdGx5IGNsZWFyIGhvdyAiam9iaWQiIGlzIHN0cnVjdGVkLCBwZXJoYXBzIGl0IGFjY291bnRzIG9mIHJlbGlzdGluZyBvZiBqb2JzPyANCg0KQWx0aG91Z2gsIHNpbmNlIHRoZSBudW1iZXIgb2YgdW5pcXVlIGpvYmlkIGlzIG9ubHkgZGlmZmVyaW5nIGZyb20gdW5pcV9pZCBieSA5MCwgaWYgdGhlIHB1cnBvc2Ugd2hlcmUgdG8gZGVwaWN0IHRoZSByZWxpc3Rpbmcgb2YgcmVzcGVjdGl2ZSBqb2JzLCBpdCBwcm9iYWJseSBkaWQgbm90IGRvIGl0cyBqb2IgdmVyeSB3ZWxsLg0KDQoNCiMjIFBheXJhdGUNCg0KYGBge3J9DQojIFNlZWluZyB0aGF0IHBheXJhdGUgbGlzdGluZyB3b3VsZCBjb3JyZXNwb25kcyB0byBmb3JtYXQgb2YgIjEsNTAsMDAwIC0gMiw1MCwwMDAgUC5BIg0KIyBJIHdvdWxkIGp1c3QgdXNlIGdzdWIgdG8gcmVtb3ZlIHN0cmluZ3MgYWZ0ZXIgUC5BDQppbnB1dGR0JHBheXJhdGUuTTwtZ3N1YiggIiBQLkEuKiQiLCAiIiwgaW5wdXRkdCRwYXlyYXRlKQ0KIyB0aGlzIGlzIGVzc2VudGlhbCB0byByZW1vdmUgbnVtZXJpY2FsIGNoYXJhY3RlcnMgYWZ0ZXIgdGhlIFAuQSBzdHJpbmcNCiMgdGhpcyBhbHNvIHJlbW92ZWQgY2VydGFpbiBsaXN0ZWQgaW5jZW50aXZlL2NvbW1pc3Npb24gaW5mb3JtYXRpb24gaW4gcGF5cmF0ZQ0KDQojIHN0cmlwcGluZyBhbGwgdGhlIG5vbiBudW1lcmljYWwgY2hyIGZyb20gdGhlIHBheXJhdGUNCmlucHV0ZHQkcGF5cmF0ZS5NPC1nc3ViKCJbXjAtOS1dIiwgIiIsIGlucHV0ZHQkcGF5cmF0ZS5NKQ0KIyBpbnB1dGR0JG0ucGF5cmF0ZTwtZ3N1YigiW14wLTksLSwtXSIsICIiLCBpbnB1dGR0JG0ucGF5cmF0ZSkNCg0KIyBPdXRsaWVycw0KIyBFeGFtcGxlIG9mIHNvbWUgcGF5cmF0ZSB0aGF0IGlzIG5vdCBleHRyYWNhdGVkIHByb3Blcmx5ICggd2l0aCAyICItIiBldGMgYWZ0ZXIgcmVnZXhwIHRyZWF0bWVudCksIA0KIyBzZWVpbmcgdGhlcmUgaXMgbm8gZWFzeSBzb2x1dGlvbiBhbmQgdGhhdCB0aGVyZSBpcyBvbmx5IH4yMDAgb2YgdGhlbSwgdGhlbSB3aWxsIGJlIGlnbm9yZWQgZm9yIG5vdw0KaW5wdXRkdFtsZW5ndGhzKHN0cnNwbGl0KGlucHV0ZHQkcGF5cmF0ZS5NLCAiLSIpKT4yLCBsaXN0KHBheXJhdGUscGF5cmF0ZS5NKSxdDQpgYGANCg0KU3BsaXR0aW5nIHRoZSBwYXlyYXRlIGNvbHVtbiB3aGlsZSBjb25kaXRpb25hbGx5IGlnbm9yaW5nIHRob3NlIHRoYXQgaGF2ZSBtb3JlIHRoYW4gZGFzaCAoIi0iKSBzeW1ib2wuDQoNCmBgYHtyfQ0KDQojIElkZW50aWZ5aW5nIHRoZSBhcHByb3ByaWF0ZSBudW1iZXIgb2Ygc3BsaXRzIGZvciBwYXlyYXRlIHdpdGggc3RyICItIg0Kc3BsaXRzIDwtIG1heChsZW5ndGhzKHN0cnNwbGl0KGlucHV0ZHQkcGF5cmF0ZS5NLCAiLSIpKSkNCg0KIyBTcGxpdGluZyB0aGUgcGF5cmF0ZSBpbnRvIG0ucGF5cmF0ZTEgYW5kIG0ucGF5cmF0ZTIsIElnbm9yaW5nIHRob3NlIGNvbHVtbiB3aXRoIGFkZGl0aW9uYWwgc3BsaXRzDQptZHQgPC0gaW5wdXRkdFtsZW5ndGhzKHN0cnNwbGl0KGlucHV0ZHQkcGF5cmF0ZS5NLCAiLSIpKTw9MiwNCiAgICAgICAgICAgICAgIHBhc3RlMCgibS5wYXlyYXRlIiwgMToyKSA6PSB0c3Ryc3BsaXQocGF5cmF0ZS5NLCAiLSIsIGZpeGVkPVRSVUUpXVtdIA0KDQojIENoYW5naW5nIHRoZSBjb2x1bW4gaW50byBudW1lcmljcw0KbWR0JG0ucGF5cmF0ZTE8LSBhcy5udW1lcmljKG1kdCRtLnBheXJhdGUxKQ0KbWR0JG0ucGF5cmF0ZTI8LSBhcy5udW1lcmljKG1kdCRtLnBheXJhdGUyKQ0KDQojIENyZWF0aW5nIGEgY29sdW1uIHRoYXQgaW5kaWNhdGUgdGhlIG1lYW4gb2YgdGhlIHBheXJhdGUsIHdvdWxkIGJlIGVhc2llciBvbiBwbG90dGluZyBldGMNCm1kdFssIG0ucGF5cmF0ZS5NZWFuIDo9IHJvd01lYW5zKC5TRCksIGJ5ID0gdW5pcV9pZCwgLlNEY29scyA9IGMoIm0ucGF5cmF0ZTEiLCAibS5wYXlyYXRlMiIpXQ0KYGBgDQoNCg0KYGBge3J9DQojIENoZWNraW5nIGlmIHRoZSB2YWx1ZXMgaW4gZXhwZXJpZW5jZTEgYXJlIGFsd2F5cyBsb3dlciB0aGFuIGV4cGVyaWVuY2UyLCANCiMgc3VjaCB0aGF0IGV4cGVyaWVuY2UxIGNhbiBiZSBjb25zaWRlcmVkIGFzIGEgbG93ZXIgbGltaXQgZm9yIHRoZSBqb2IsIHZpY2UgdmVyc2EgZm9yIGV4cGVyaWVuY2UyDQpzdW1tYXJ5KG1kdCRtLnBheXJhdGUxPm1kdCRtLnBheXJhdGUyKQ0KIyAgICBNb2RlICAgRkFMU0UgICAgVFJVRSAgICBOQSdzIA0KIyBsb2dpY2FsICAgICAgIDIgICAgNDc0MyAgIDE3MjU1IA0KDQojIGxpc3RpbmcgdGhlIGRhdGEgd2l0aCBtLnBheXJhdGUxID4gbS5wYXlyYXRlMg0KbWR0W20ucGF5cmF0ZTE+bS5wYXlyYXRlMixsaXN0KHBheXJhdGUscGF5cmF0ZS5NLG0ucGF5cmF0ZTEsbS5wYXlyYXRlMildDQpgYGANCg0KSW4gYm90aCBvZiB0aGVzZSBjYXNlcywgdGhlIHBheXJhdGUgaXMgbm90IHdlbGwgZm9ybWF0ZWQgYW5kIGV4dHJhY3RlZC4gVGhlcmUgaXMgcHJvYmFibHkgc29tZSBiYWQgZXh0cmFjdGlvbiBpbiB0aGUgZXh0cmFjdGluZyBvZiBwYXlyYXRlIHRoYXQgaXMgbm90IGRlcGljdGVkIGhlcmUgYXMgd2VsbCBhcyB0aGV5IG1heSBiZSBoaWRkZW4gYmVoaW5kIG0ucGF5cmF0ZTIgPiBtLnBheXJhdGUxLg0KDQpSZW1vdmluZyB0aGVzZSBjYXNlcyAod2hlcmUgbS5wYXlyYXRlMT5tLnBheXJhdGUyKSBieSBzZXR0aW5nIHRoZW0gdG8gTkENCg0KDQpgYGB7cn0NCm1kdFsobS5wYXlyYXRlMT5tLnBheXJhdGUyKSxgOj1gKHBheXJhdGUuTT0gTkEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtLnBheXJhdGUxPSBOQSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG0ucGF5cmF0ZTI9IE5BLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbS5wYXlyYXRlLk1lYW49IE5BKV0NCg0KDQojIFRoZSBtYWpvcml0eSBvZiB0aGUgcmVjb3JkcyBoYXMgbm8gcGF5cmF0ZSBsaXN0ZWQuDQojIHN1bW1hcnkobWR0JG0ucGF5cmF0ZS5NZWFuKQ0KIyAgICAgIE1pbi4gICAxc3QgUXUuICAgIE1lZGlhbiAgICAgIE1lYW4gICAzcmQgUXUuICAgICAgTWF4LiAgICAgIE5BJ3MgDQojIDkuMDAwZSswMCAyLjUwMGUrMDUgMy43NTBlKzA1IDMuNjcyZSswOSA4LjAwMGUrMDUgMS43NDBlKzEzICAgICAxNzI1NSANCiMgVGhlIG1heGltdW0gZmlndXJlIGFsc28gcG9pbnRzIHRvIGxpa2VseSBlcnJvciBpbiBleHRyYWNhdGlvbg0KDQojIGFmdGVyIGF0dGVtcHRpbmcgYSBmZXcgdGhyZXNob2xkIG9mIGN1dCwgSSBmaW5kIDllKzYgdG8gYmUgbW9zdCBzdWl0YWJsZSwgbGlzdGVkIGJlbG93IGFyZSBhIHRhYmxlIGRlcGljdGVkIHBheXJhdGUgYWZ0ZXIgdGhpcyB0aHJlc2hvbGQuDQptZHRbbS5wYXlyYXRlLk1lYW4+OWUrNl0gJT4lDQogIC5bLGxpc3QocGF5cmF0ZSwgbS5wYXlyYXRlLk1lYW4pXQ0KDQpgYGANCg0KYGBge3J9DQptZHRbbS5wYXlyYXRlLk1lYW4+OWUrNixgOj1gKHBheXJhdGUuTT0gTkEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG0ucGF5cmF0ZTE9IE5BLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtLnBheXJhdGUyPSBOQSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbS5wYXlyYXRlLk1lYW49IE5BKV0NCg0KDQojdG90YWwgY291bnQgb2YgdmFsaWQgcGF5cmF0ZSByZWNvcmRzDQpzdW0oIWlzLm5hKG1kdCRtLnBheXJhdGUuTWVhbikpDQpgYGANCg0KDQoNCmBgYHtyfQ0KIyBhbGxfdmFsdWVzIDwtIGZ1bmN0aW9uKHgpIHsNCiMgICBpZihpcy5udWxsKHgpKSByZXR1cm4oTlVMTCkNCiMgICBwYXN0ZTAobmFtZXMoeCksICI6ICIsIGZvcm1hdCh4KSwgY29sbGFwc2UgPSAiPGJyIC8+IikNCiMgfQ0KDQptZHRbIWlzLm5hKG0ucGF5cmF0ZS5NZWFuKSxdW20ucGF5cmF0ZS5NZWFuPDllKzZdICU+JQ0KICBtZWx0KC4sICBtZWFzdXJlLnZhcnMgPSBwYXR0ZXJucygiXm0ucGF5cmF0ZSIpKSAlPiUNCiAgZ2d2aXMofnZhbHVlLCBmaWxsID0gfnZhcmlhYmxlKSAlPiUNCiAgZ3JvdXBfYnkodmFyaWFibGUpICU+JQ0KICBsYXllcl9kZW5zaXRpZXMoKSAgDQoNCmBgYA0KDQoNCiMjIHNraWxscw0KDQpgYGB7cn0NCmhlYWQoaW5wdXRkdCRza2lsbHMpDQoNCnVuaXF1ZShpbnB1dGR0JHNraWxscykNCg0KDQppbnB1dGR0WywuTixieT1saXN0KHNraWxscyldICU+JQ0KICAuWywuU0Rbb3JkZXIoLU4pXV0gJT4lDQogIC5bLCBoZWFkKC5TRCwgMTApLF0lPiUNCiAgZ2d2aXMoeD1+c2tpbGxzLCB5PX5OKSAlPiUNCiAgbGF5ZXJfYmFycygpJT4lDQogIGFkZF9heGlzKCJ4IiwgcHJvcGVydGllcyA9IGF4aXNfcHJvcHMoDQogICAgbGFiZWxzID0gbGlzdChhbmdsZSA9IDQ1LCBhbGlnbiA9ICJsZWZ0IiwgZm9udFNpemUgPSAxMCkNCiAgKSkNCg0KDQoNCmBgYA0KDQojIyBpbmR1c3RyeQ0KYGBge3J9DQoNCiMgQ2hlY2tpbmcgd2hldGhlciBldmVyeSB2YWwgIGlkIGVudHJ5IGhhcyAieXJzIiBpbiB0aGUgcmVjb3JkcywgDQpzdW1tYXJ5KHN0cl9kZXRlY3QobWR0JGV4cGVyaWVuY2UsICJ5cnMiKSkgIA0KDQpzcGxpdHMgPC0gbWF4KGxlbmd0aHMoc3Ryc3BsaXQobWR0JGluZHVzdHJ5LCAiLyIpKSkNCm1kdCA8LSBtZHRbLHBhc3RlMCgiaW5kdXN0cnlTIiwgMTpzcGxpdHMpIDo9IHRzdHJzcGxpdChpbmR1c3RyeSwgIi8iLCBmaXhlZD1UUlVFKV1bXQ0KDQptZWx0KG1kdCwgIG1lYXN1cmUudmFycyA9IHBhdHRlcm5zKCJeaW5kdXN0cnlTIikpDQpgYGANCg0KIyMgRXhwZXJpZW5jZQ0KDQpgYGB7cn0NCiMgZHRbIXN0cl9kZXRlY3QoZHQkZXhwZXJpZW5jZSwgInlycyIpXSMgSXQgc2VlbXMgYWxsIHRoZSBsaXN0aW5nIGFyZSBpbiBhIG1hdHRlciBvZiB5ZWFycw0KDQojIG9uZSBlbnRyeSB3aXRoICBkb3VibGUgZGFzaCAiLSINCiMgZ2xpbXBzZShkdFttYXgobGVuZ3RocyhzdHJzcGxpdChkdCRleHBlcmllbmNlLCAiLSIpKSk9PWxlbmd0aHMoc3Ryc3BsaXQoZHQkZXhwZXJpZW5jZSwgIi0iKSldKQ0KbWR0W21heChsZW5ndGhzKHN0cnNwbGl0KG1kdCRleHBlcmllbmNlLCAiLSIpKSk9PWxlbmd0aHMoc3Ryc3BsaXQobWR0JGV4cGVyaWVuY2UsICItIikpXSRleHBlcmllbmNlIDwtICIxIC0gMyB5cnMiDQoNCiMgUmVtb3ZpbmcgdGhlIGxhc3QgMyBjaGFyIC0gInlycyIgaW4gdGhlIGV4cGVyaWVuY2UgcmVjb3Jkcw0KIyBuY2hhcihtZHQkZXhwZXJpZW5jZSwgYWxsb3dOQSA9IFRSVUUpLTMNCm1kdCRleHBlcmllbmNlLk0gPC0gc3RyX3N1YihtZHQkZXhwZXJpZW5jZSwgMSxuY2hhcihtZHQkZXhwZXJpZW5jZSwgYWxsb3dOQSA9IFRSVUUpLTQpDQoNCiMgU3BsaXR0aW5nIHRoZSBleHBlcmllbmNlDQpzcGxpdHMgPC0gbWF4KGxlbmd0aHMoc3Ryc3BsaXQobWR0JGV4cGVyaWVuY2UuTSwgIi0iKSkpDQptZHQgPC0gbWR0WyxwYXN0ZTAoImV4cGVyaWVuY2UiLCAxOnNwbGl0cykgOj0gdHN0cnNwbGl0KGV4cGVyaWVuY2UuTSwgIi0iLCBmaXhlZD1UUlVFKV1bXQ0KDQojIENoYW5naW5nIHRoZW0gdG8gYXBwcm9wcmlhdGUgY2xhc3MNCm1kdCRleHBlcmllbmNlMTwtYXMubnVtZXJpYyhtZHQkZXhwZXJpZW5jZTEpDQptZHQkZXhwZXJpZW5jZTI8LWFzLm51bWVyaWMobWR0JGV4cGVyaWVuY2UyKQ0KDQojIENoZWNraW5nIGlmIHRoZSB2YWx1ZXMgaW4gZXhwZXJpZW5jZTEgYXJlIGFsd2F5cyBsb3dlciB0aGFuIGV4cGVyaWVuY2UyLCANCiMgc3VjaCB0aGF0IGV4cGVyaWVuY2UxIGNhbiBiZSBjb25zaWRlcmVkIGFzIGEgbG93ZXIgbGltaXQgZm9yIHRoZSBqb2IsIHZpY2UgdmVyc2EgZm9yIGV4cGVyaWVuY2UyDQojIHN1bW1hcnkobWR0JGV4cGVyaWVuY2UxPD1tZHQkZXhwZXJpZW5jZTIpDQojICAgIE1vZGUgICAgVFJVRSAgICBOQSdzIA0KIyBsb2dpY2FsICAgMjE4ODUgICAgIDExNSANCg0KIyByZW5hbWluZyB0aGUgZXhwZXJpZW5jZTEgYW5kIGV4cGVyaWVuY2UyIGNvbHVtbiBpbnRvIG1vcmUgb2J2aW91cyBmb3JtDQpzZXRuYW1lcyhtZHQsIGMoImV4cGVyaWVuY2UxIiwiZXhwZXJpZW5jZTIiKSwgYygibS5leHBlcmllbmNlLkwiLCAibS5leHBlcmllbmNlLlUiKSkNCg0KDQptZHRbIWlzLm5hKG0uZXhwZXJpZW5jZS5MKSxdJT4lDQogIG1lbHQoLiwgIG1lYXN1cmUudmFycyA9IHBhdHRlcm5zKCJebS5leHBlcmllbmNlIikpICU+JQ0KICBnZ3Zpcyh+dmFsdWUsIGZpbGwgPSB+dmFyaWFibGUpICU+JQ0KICBncm91cF9ieSh2YXJpYWJsZSkgJT4lDQogIGxheWVyX2RlbnNpdGllcyhhZGp1c3QgPSAyKSAgDQoNCmBgYA0KDQojIyBEYXRlIFRpbWUNCg0KYGBge3J9DQpoZWFkKG1kdCRwb3N0ZGF0ZSkNCg0KI3N0ciBzcGxpdCB0aGUgcG9zdGRhdGUgYmFzZWQgb24gZm9ybWF0IGNvcnJlc3BvbmRzIHRvICIyMDE2LTA1LTIxIDE5OjMwOjAwICswMDAwIiBvbiAiKyINCm1kdFssIGMoInBvc3RkYXRlX3RpbWUiLCJwb3N0ZGF0ZV90aW1lem9uZSIpIDo9IHRzdHJzcGxpdChwb3N0ZGF0ZSwiKyIsMildDQoNCiMgc3VtbWFyeShhcy5mYWN0b3IobWR0JHBvc3RkYXRlX3RpbWV6b25lKSkNCiMgMDAwMCAgTkEncyANCiMgMjE5NzcgICAgMjMgDQojIGl0IHNlZW1zIHRpbWV6b25lIGlzIHByZXR0eSBtZWFuaW5nbGVzcyBpbiB0aGUgZGF0YSBiYXNlIHRvbw0KDQptZHQkcG9zdGRhdGVfdGltZXpvbmUgPC0gTlVMTA0KYGBgDQoNCiMjIyBDaGFuZ2luZyB0aGUgZGF0ZXRpbWUgaW50byBhcHByb3ByaWF0ZSBmb3JtYXQNCg0KYGBge3J9DQptZHQkcG9zdGRhdGVfdGltZSA8LSBwYXJzZV9kYXRlX3RpbWUobWR0JHBvc3RkYXRlX3RpbWUsICIlWS0lbS0lZCBIOk06UyIpDQojaWdub3JpbmcgdGltZXpvbmUgZm9yIG5vdywgc2hvdWxkbid0IG1ha2UgYW55IGRpZmZlcmVuY2UgdG9vDQpgYGANCg0KIyMgTG9jYXRpb24NCg0KYGBge3J9DQojIHNvbWUgam9iIGxpc3RpbmcgYXJlIGxpc3RlZCBhdCBtdWx0aXBsZSBsb2NhdGlvbg0KIyAiRGVsaGkgTkNSLCBNdW1iYWksIEJlbmdhbHVydSwgS29jaGksIEdyZWF0ZXIgTm9pZGEsIEd1cmdhb24sIEh5ZGVyYWJhZCwgS296aGlrb2RlLCBMdWNrbm93Ig0KDQpzcGxpdHMgPC0gbWF4KGxlbmd0aHMoc3Ryc3BsaXQobWR0JGpvYmxvY2F0aW9uX2FkZHJlc3MsICIsIikpKQ0KDQojIGNoZWNraW5nIA0KIyBtZHRbbGVuZ3RocyhzdHJzcGxpdChtZHQkam9ibG9jYXRpb25fYWRkcmVzcywgIiwiKSk9PXNwbGl0c10NCg0KbWR0IDwtIG1kdFsscGFzdGUwKCJsb2NhdGlvblMiLCAxOnNwbGl0cykgOj0gdHN0cnNwbGl0KGpvYmxvY2F0aW9uX2FkZHJlc3MsICIsIiwgZml4ZWQ9VFJVRSldICU+JQ0KICANCiAgDQogIG1lbHQobWR0LCAgbWVhc3VyZS52YXJzID0gcGF0dGVybnMoIl5sb2NhdGlvblMiKSkNCg0KDQpkdC5sb2NhdGlvbiA8LSBtZHRbLHBhc3RlMCgibG9jYXRpb24iLCAxOnNwbGl0cykgXVssIElEIDo9IE5VTExdDQojIA0KIyAjIFNvcnQgdGVybV9mcmVxdWVuY3kgaW4gZGVzY2VuZGluZyBvcmRlcg0KIyB0ZXJtX2ZyZXF1ZW5jeTwtIHNvcnQobWR0JGxvY2F0aW9uLGRlY3JlYXNpbmcgPSBUKQ0KIyANCiMgIyBQbG90IGEgYmFyY2hhcnQgb2YgdGhlIDEwIG1vc3QgY29tbW9uIHdvcmRzDQojIGJhcnBsb3QodGVybV9mcmVxdWVuY3lbMToxMF0sIGNvbCA9ICJ0YW4iLCBsYXMgPSAyKQ0KDQoNCmBgYA0KDQoNCiMjIEpvYiBkZXNjcmlwdGlvbg0KDQpgYGB7cn0NCnJlcXVpcmUodG0pDQpyZXF1aXJlKHF1YW50ZWRhKQ0KcmVxdWlyZSh3b3JkY2xvdWQpDQpyZXF1aXJlKHNsYW0pICMgZm9yIHJvd19zdW1zDQpgYGANCg0KDQpgYGB7cn0NCmNsZWFuX1ZDIDwtIFZDb3JwdXMoVmVjdG9yU291cmNlKG1kdCRqb2JkZXNjcmlwdGlvbikpICU+JQ0KICB0bV9tYXAocmVtb3ZlUHVuY3R1YXRpb24pICU+JQ0KICB0bV9tYXAocmVtb3ZlTnVtYmVycykgJT4lDQogIHRtX21hcCh0b2xvd2VyKSAgJT4lDQogIHRtX21hcChyZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCJlbmdsaXNoIikpICU+JQ0KICB0bV9tYXAoc3RyaXBXaGl0ZXNwYWNlKSAlPiUNCiAgdG1fbWFwKFBsYWluVGV4dERvY3VtZW50KSAlPiUNCiAgdG1fbWFwKHN0ZW1Eb2N1bWVudCwgbGFuZ3VhZ2UgPSAiZW5nbGlzaCIpDQoNCmNsZWFuX1ZDLnRkbSA8LSBUZXJtRG9jdW1lbnRNYXRyaXgoY2xlYW5fVkMpDQoNCiMgQ2FsY3VsYXRlIHRoZSByb3dTdW1zOiB0ZXJtX2ZyZXF1ZW5jeQ0KIyBOb3RpY2UgdGhhdCB0ZG0gaXMgYWN0dWFsbHkgc2ltcGxlLXRyaXBsZXRfbWF0cml4IGNsYXNzLA0KIyB3aGljaCBpcyByZWNvcmQgYXMgc3BhcnNlIG1hdHJpeCBhbmQgaGFzIGl0cyBvd24gcm93c3VtIGZ1bmN0aW9uDQojIGNsYXNzKGNsZWFuX1ZDLnRkbSkNCiMgICJUZXJtRG9jdW1lbnRNYXRyaXgiICAgICJzaW1wbGVfdHJpcGxldF9tYXRyaXgiDQoNCnRlcm1fZnJlcXVlbmN5PC1yb3dfc3VtcyhjbGVhbl9WQy50ZG0pDQoNCg0KIyBTb3J0IHRlcm1fZnJlcXVlbmN5IGluIGRlc2NlbmRpbmcgb3JkZXINCnRlcm1fZnJlcXVlbmN5PC0gc29ydCh0ZXJtX2ZyZXF1ZW5jeSxkZWNyZWFzaW5nID0gVCkNCg0KIyBQbG90IGEgYmFyY2hhcnQgb2YgdGhlIDEwIG1vc3QgY29tbW9uIHdvcmRzDQpiYXJwbG90KHRlcm1fZnJlcXVlbmN5WzE6MTBdLCBjb2wgPSAidGFuIiwgbGFzID0gMikNCg0KIyBDcmVhdGUgd29yZF9mcmVxcw0Kd29yZF9mcmVxcyA8LSBkYXRhLmZyYW1lKHRlcm0gPSBuYW1lcyh0ZXJtX2ZyZXF1ZW5jeSksIG51bSA9IHRlcm1fZnJlcXVlbmN5KQ0KaGVhZCh3b3JkX2ZyZXFzKQ0KDQoNCndvcmRjbG91ZCh3b3JkX2ZyZXFzJHRlcm0sIHdvcmRfZnJlcXMkbnVtLCBtYXgud29yZHMgPSAxMDAsIA0KICAgICAgICAgICByYW5kb20ub3JkZXI9RkFMU0UsIHJvdC5wZXI9MC4zNSwgY29sb3JzPWJyZXdlci5wYWwoOCwiRGFyazIiKSkNCmBgYA0KDQpgYGB7cn0NCg0KY2xlYW5fVkNfc3BhcnNlX3I0MDwtcmVtb3ZlU3BhcnNlVGVybXMoY2xlYW5fVkMudGRtLCAwLjQpDQp0ZG08LXNvcnQocm93U3Vtcyhhcy5tYXRyaXgoY2xlYW5fVkNfc3BhcnNlX3I0MCkpLCBkZWNyZWFzaW5nICA9IFRSVUUpDQp3b3JkX2ZyZXFzIDwtIGRhdGEuZnJhbWUodGVybSA9IG5hbWVzKHRkbSksIG51bSA9IHRkbSkNCg0Kc2V0LnNlZWQoMTQyKSAgIA0Kd29yZGNsb3VkKHdvcmRfZnJlcXMkdGVybSwgd29yZF9mcmVxcyRudW0sIG1pbi5mcmVxPTIwLCBzY2FsZT1jKDUsIC4xKSwgY29sb3JzPWJyZXdlci5wYWwoNiwgIkRhcmsyIikpICAgDQpgYGANCg0KYGBge3J9DQpjbGVhbl9WQ19zcGFyc2VfcjEwPC1yZW1vdmVTcGFyc2VUZXJtcyhjbGVhbl9WQy50ZG0sIDAuMSkNCiB0ZG08LWFzLm1hdHJpeChjbGVhbl9WQ19zcGFyc2VfcjEwKQ0KIA0KY29tcGFyaXNvbi5jbG91ZCh0ZG0sIGNvbG9ycyA9IGMoIm9yYW5nZSIsICJibHVlIiksIG1heC53b3JkcyA9IDUwKQ0KYGBgDQoNCg0KYGBge3J9DQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCmNvbW1vbmFsaXR5LmNsb3VkKHRkbSwgcmFuZG9tLm9yZGVyPUZBTFNFLCBzY2FsZT1jKDUsIC41KSxjb2xvcnMgPSBicmV3ZXIucGFsKDQsICJEYXJrMiIpLCBtYXgud29yZHM9NDAwKQ0KYGBgDQoNCg0KYGBge3J9DQpkb2NzIDwtVkMgJT4lDQogIHRtX21hcChyZW1vdmVQdW5jdHVhdGlvbikgJT4lDQogIHRtX21hcChyZW1vdmVOdW1iZXJzKSAlPiUNCiAgdG1fbWFwKHRvbG93ZXIpICAlPiUNCiAgdG1fbWFwKHJlbW92ZVdvcmRzLCBzdG9wd29yZHMoImVuZ2xpc2giKSkgJT4lDQogIHRtX21hcChzdHJpcFdoaXRlc3BhY2UpICU+JQ0KICB0bV9tYXAoUGxhaW5UZXh0RG9jdW1lbnQpDQoNCm15Q29ycHVzQ29weTwtIGRvY3MgIyMgY3JlYXRpbmcgYSBjb3B5IHRvIGJlIHVzZWQgYXMgYSBkaWN0aW9uYXJ5DQoNCiMgZG9jcyA8LSBkb2NzICU+JQ0KIyAgIHRtX21hcChzdGVtRG9jdW1lbnQpICU+JQ0KIyAgIHRtX21hcChzdGVtQ29tcGxldGlvbiwgZGljdGlvbmFyeSA9IG15Q29ycHVzQ29weSkNCg0KZG9jcy50ZG08LVRlcm1Eb2N1bWVudE1hdHJpeChkb2NzKQ0KDQp0ZXJtX2ZyZXF1ZW5jeTwtcm93X3N1bXMoY2xlYW5fVkMudGRtKQ0KDQojIFNvcnQgdGVybV9mcmVxdWVuY3kgaW4gZGVzY2VuZGluZyBvcmRlcg0KdGVybV9mcmVxdWVuY3k8LSBzb3J0KHRlcm1fZnJlcXVlbmN5LGRlY3JlYXNpbmcgPSBUKQ0KDQojIFBsb3QgYSBiYXJjaGFydCBvZiB0aGUgMTAgbW9zdCBjb21tb24gd29yZHMNCmJhcnBsb3QodGVybV9mcmVxdWVuY3lbMToxMF0sIGNvbCA9ICJ0YW4iLCBsYXMgPSAyKQ0KDQpyZXF1aXJlKHdvcmRjbG91ZCkNCg0KIyBDcmVhdGUgd29yZF9mcmVxcw0Kd29yZF9mcmVxcyA8LSBkYXRhLmZyYW1lKHRlcm0gPSBuYW1lcyh0ZXJtX2ZyZXF1ZW5jeSksIG51bSA9IHRlcm1fZnJlcXVlbmN5KQ0KaGVhZCh3b3JkX2ZyZXFzKQ0KDQoNCndvcmRjbG91ZCh3b3JkX2ZyZXFzJHRlcm0sIHdvcmRfZnJlcXMkbnVtLCBtYXgud29yZHMgPSAxMDAsIA0KICAgICAgICAgICByYW5kb20ub3JkZXI9RkFMU0UsIHJvdC5wZXI9MC4zNSwgY29sb3JzPWJyZXdlci5wYWwoOCwiRGFyazIiKSkNCg0KDQoNCmBgYA0KDQoNCmBgYHtyfQ0KaW5hdWdkZm0gPC0gZGZtKG1kdCRqb2JkZXNjcmlwdGlvbiwgcmVtb3ZlID0gc3RvcHdvcmRzKCJlbmdsaXNoIiksIA0KICAgICAgICAgICAgICAgIHN0ZW0gPSBUUlVFLCBncm91cHM9bWR0JGluZHVzdHJ5MSwgbmdyYW1zID0gMikNCmluYXVnZGZtIDwtIGRmbShtZHQkam9iZGVzY3JpcHRpb24pDQogICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgDQp0ZG1fdHJpbTwtdChkZm1fdHJpbShpbmF1Z2RmbSwgbWF4X2RvY2ZyZXEgPSAuOCkpDQpoZWFkKHRkbV90cmltKQ0KDQp0ZXJtX2ZyZXF1ZW5jeTwtcm93X3N1bXModGRtX3RyaW0pDQoNCiMgU29ydCB0ZXJtX2ZyZXF1ZW5jeSBpbiBkZXNjZW5kaW5nIG9yZGVyDQp0ZXJtX2ZyZXF1ZW5jeTwtIHNvcnQodGVybV9mcmVxdWVuY3ksZGVjcmVhc2luZyA9IFQpDQoNCndvcmRfZnJlcXMgPC0gZGF0YS5mcmFtZSh0ZXJtID0gbmFtZXModGVybV9mcmVxdWVuY3kpLCBudW0gPSB0ZXJtX2ZyZXF1ZW5jeSkNCg0Kd29yZGNsb3VkKHdvcmRfZnJlcXMkdGVybSwgd29yZF9mcmVxcyRudW0sIG1heC53b3JkcyA9IDEwMCwgDQogICAgICAgICAgIHJhbmRvbS5vcmRlcj1GQUxTRSwgcm90LnBlcj0wLjM1LCBjb2xvcnM9YnJld2VyLnBhbCg4LCJEYXJrMiIpKQ0KDQoNCmBgYA0KDQpgYGB7cn0NCmRvY3M8LWMobWR0JGpvYmRlc2NyaXB0aW9uLCBtZHQkaW5kdXN0cnkxKQ0KZGF0YShkYXRhX2NvcnB1c19pbmF1Z3VyYWwpDQoNCg0KZGF0YS5mcmFtZShpbmF1Z1NwZWVjaCA9IHRleHRzKG1kdCRqb2JkZXNjcmlwdGlvbiksIGRvY3ZhcnMoYXMuZmFjdG9yKG1kdCRpbmR1c3RyeTEpKSkNCiAgICAgICAgICAgIA0KICAgICAgICAgICAgDQpteUNvcnB1cyA8LSBjb3JwdXNfc3Vic2V0KGRhdGEuZnJhbWUobWR0JGpvYmRlc2NyaXB0aW9uLCkNCg0KDQpteVN0ZW1NYXQgPC0gZGZtKG15Q29ycHVzLCByZW1vdmUgPSBzdG9wd29yZHMoImVuZ2xpc2giKSwgc3RlbSA9IFRSVUUsIHJlbW92ZV9wdW5jdCA9IFRSVUUpDQpteVN0ZW1NYXRbLCAxOjVdDQoNCmBgYA0KDQo=